/*
 * VectorRegistros.cpp
 *
 */
#include "VectorRegistros.h"


VectorRegistros::VectorRegistros(size_t tamBytes, unsigned long int selfRef){
	this->_espacioLibre = tamBytes;
	this->_cantBytesOcupados =0;
	this->_referenciaExtra = -1;
	this->_selfRef = selfRef;
}


VectorRegistros::~VectorRegistros(){
	this->_espacioLibre = 0;
	this->_cantBytesOcupados =0;
	this->_referenciaExtra = -1;
	this->_selfRef = -1;

	// Libera el contenido de la lista
	std::list<RegistroAbstracto*>::iterator it;
	for (it=this->_listRegs.begin(); it != this->_listRegs.end(); it++){
		delete (*it);
	}

	// Limpia toda la lista
	this->_listRegs.clear();
}


unsigned long int VectorRegistros::getSelfReference(){
	return this->_selfRef;
}


resulOperacion VectorRegistros::addRegistro(RegistroAbstracto* reg){

	// No se permiten registros repetidos
	if ( this->existeRegistro(reg->getId()) ) return ERROR;

	// Itera a travez de todos los registros
	std::list<RegistroAbstracto*>::iterator it;
	for (it = this->_listRegs.begin(); it != this->_listRegs.end(); it++){

		// El registro apuntado por el iterator
		RegistroAbstracto* regTemp = *it;

		// Busca un registro con "id" mayor
		if ( regTemp->getId() > reg->getId() ) break;
	}

	// Si encontró la posicion donde insertar el registro
	if (it != this->_listRegs.end()){
		// Inserta el registro antes del apuntado
		this->_listRegs.insert(it, reg);
	} else {
		// No se encontro un registro con valor mayor, lo agregar al final
		this->_listRegs.push_back(reg);
	}

	// Actualiza los parametros del contenedor
	this->_espacioLibre -= reg->getSize();
	this->_cantBytesOcupados += reg->getSize();

	// Devuelve el resultado de la operacion
	if ( this->_espacioLibre <=0 ) return OVERFLOW;
	else return OK;
}


// Chequea si existe underflow en la lista
bool VectorRegistros::hayUnderFlow(){
	// Si el espacio libre es mayor que el espacio ocupado hay underflow
	return (this->_espacioLibre > this->_cantBytesOcupados);
}


resulOperacion VectorRegistros::removeRegistro(unsigned long int id){

	// Tiene que encontrar el registro a borrar
	std::list<RegistroAbstracto*>::iterator it;
	for (it = this->_listRegs.begin(); it != this->_listRegs.end(); it++){
		RegistroAbstracto* reg = *it;

		// Si tiene el mismo Id hay que borrarlo
		if ( reg->getId() == id ){
			// Primero se actualizan los datos
			this->_espacioLibre += reg->getSize();
			this->_cantBytesOcupados -= reg->getSize();

			// Se borran los elementos
			delete reg;
			this->_listRegs.erase(it);

			// Chequea el resultado de la operacion
			if ( hayUnderFlow() ) return UNDERFLOW;
			else return OK;
		}
	}

	// No se encontro el elemento a borrar
	return ERROR;
}


RegistroAbstracto* VectorRegistros::getRegistro(unsigned long int id){

	// Busca el registro
	std::list<RegistroAbstracto*>::iterator it;
	for (it = this->_listRegs.begin(); it != this->_listRegs.end(); it++){
		RegistroAbstracto* reg = *it;
		if ( reg->getId() == id ) return *it;
	}

	return NULL;
}


size_t VectorRegistros::getSize(){
	// El tamaño para la metadata del contenedor
	size_t tam = sizeof(this->_espacioLibre) + sizeof(this->_cantBytesOcupados);
	
	// El tamaño de la referencia extra
	tam += sizeof(this->_referenciaExtra);

	// El tamaño de los registros
	tam += this->_cantBytesOcupados;

	return tam;
}


bool VectorRegistros::existeRegistro(unsigned long int id){
	// Recorre todos los elementos buscando uno que tenga el ud señalado
	std::list<RegistroAbstracto*>::iterator it;
	for (it = this->_listRegs.begin(); it != this->_listRegs.end(); it++){

		RegistroAbstracto* reg = *it;
		if (reg->getId() == id) return true;
	}

	// No se encontro
	return false;
}


size_t VectorRegistros::getCantRegistros(){
	return this->_listRegs.size();
}


RegistroAbstracto* VectorRegistros::operator[](int i){
	std::list<RegistroAbstracto*>::iterator it = this->_listRegs.begin();
	for (int j=0; j<i; j++) it++;

	return *it;
}


unsigned long int VectorRegistros::busquedaNodoHoja(unsigned long int id, bool inexacta){

	// Busca un registro donde el "id" sea mayor o igual al pedido
	int pos =0;
	std::list<RegistroAbstracto*>::iterator it;
	for (it = this->_listRegs.begin(); it != this->_listRegs.end(); it++){

		// Castea a "RegistroAbstracto*" para acceder a los metodos
		if (static_cast<RegistroAbstracto*>(*it)->getId() >= id) break;
	
		// Aumenta el contador de posicion
		pos++;
	}

	// Simplificación
	RegistroAbstracto* reg = *it;

	// Encontré el registro buscado
	if (reg->getId() == id) return pos;
	else	// Encontre uno mayor y la busqueda es in-exacta
		if (inexacta && reg->getId() > id) return pos;
	else	// No encontré nada o encontré uno mayor y la busqueda es exacta
		return -1;
}


// En los nodos internos no existe la busqueda exacta o in-exacta
unsigned long int VectorRegistros::busquedaNodoInterno(unsigned long int id){
	std::list<RegistroAbstracto*>::iterator it;
	for (it = this->_listRegs.begin(); it != this->_listRegs.end(); it++){

		// Termina la busqueda cuando encuentra el primero que es igual o mayor
		RegistroAbstracto* reg = *it;
		if (reg->getId() >= id)
			return dynamic_cast<RegistroInterno*>(*it)->getRefhijo();
	}

	// No se encontró el registro buscado,
	// debe ser mayor a todos los que estan en el contenedor
	// Se devuelve la referencia al nodo que contiene los id mayores
	return this->_referenciaExtra;
}


unsigned long int VectorRegistros::buscarRegistro(unsigned long int id, int nivel, bool inexacta){

	// La forma de la busqueda cambia si es nodo hoja o nodo interno
	if (nivel == 0)
		return busquedaNodoHoja(id, inexacta);
	else
		return busquedaNodoInterno(id);
}


unsigned long int VectorRegistros::getRefProxNodo(int nivelNodo){
	// Si es hoja su nivel es igual a cero
	if (nivelNodo !=0) return -1;
	else return this->_referenciaExtra;
}


void VectorRegistros::setRefProxNodo(unsigned long int ref, int nivelNodo){
	if (nivelNodo == 0) this->_referenciaExtra = ref;
}


unsigned long int VectorRegistros::getRefenciaDerecha(int nivelNodo){
	// Si es nodo interno su nivel es distinto de cero
	if (nivelNodo ==0) return -1;
	else return this->_referenciaExtra;
}


void VectorRegistros::setRefenciaDerecha(unsigned long int refDerecha, int nivelNodo){
	if (nivelNodo != 0) this->_referenciaExtra = refDerecha;
}

// Pasan los elementos mayores
RegistroInterno* VectorRegistros::split(VectorRegistros* newContenedor, int nivelNodo){

	// Calcula la posicion desde donde se deben copiar los elementos
	// al nuevo contenedor
	int cantElems = this->_listRegs.size()/2;
	std::list<RegistroAbstracto*>::iterator it = this->_listRegs.begin();
	for (int i=0; i< cantElems; i++) it++;

	// Mueve los elementos al nuevo contenedor
	for (std::list<RegistroAbstracto*>::iterator it2 = it; it2 != this->_listRegs.end(); it2++){
		RegistroAbstracto* reg = *it2;
		newContenedor->addRegistro(reg);

		// Actualiza los valores de control de espacio
		this->_espacioLibre += reg->getSize();
		this->_cantBytesOcupados -= reg->getSize();
	}

	// Elimina los registros de la lista local que se pasaron al nuevo contenedor
	this->_listRegs.erase(it, this->_listRegs.end());

	// Los datos a promover salen del ultimo registro del contenedor
	it--;
	RegistroAbstracto* regDatos = *it;
	RegistroInterno* regPromovido;

	// El resto del proceso cambia si se trata de una hoja o un nodo interno
	if (nivelNodo == 0){ 	// Es hoja

		// Actualiza las referencias a las proximas hojas
		newContenedor->setRefProxNodo(this->_referenciaExtra, nivelNodo);
		this->_referenciaExtra = newContenedor->getSelfReference();

		// Se crea el registro a promover, como es arbol b+ siempre es RegistroInterno
		regPromovido = new RegistroInterno();
		regPromovido->setId(regDatos->getId());
		regPromovido->setRefHijo(this->_selfRef);

	} else { 	// Es nodo interno

		// Copia la referencia a los punteros con los mayores valores al nuevo contenedor
		newContenedor->setRefenciaDerecha(this->_referenciaExtra, nivelNodo);

		// Actualiza la referencia extra
		regPromovido = static_cast<RegistroInterno*>(regDatos);
		this->_referenciaExtra = regPromovido->getRefhijo();

		// Promueve el ultimo registro del contenedor actual
		regPromovido->setRefHijo(this->_selfRef);
		this->_listRegs.erase(it);
	}

	// Como se realizó un split existe un registro que se promueve
	return regPromovido;
}


RegistroInterno* VectorRegistros::mergeTo(VectorRegistros* nuevoContenedor, int nivelNodo){

	// Copia los registros al nuevo contenedor 
	std::list<RegistroAbstracto*>::iterator it;
	for (it = this->_listRegs.begin(); it != this->_listRegs.end(); it++){

		// Pasa el puntero al registro al nuevo contenedor
		nuevoContenedor->addRegistro(*it);
	}

	// Vacia la lista vieja de registros
	this->_listRegs.clear();

	RegistroInterno* regRetorno = NULL;

	// Si es una hoja
	if (nivelNodo == 0){
		// Copia la referencia al siguiente nodo
		nuevoContenedor->setRefProxNodo(this->_referenciaExtra, nivelNodo);

		// Los datos del maximo registro hoja
		RegistroAbstracto* regData = (*nuevoContenedor)[nuevoContenedor->getCantRegistros()];

		// Crea un nodo interno que referencie a la hoja
		regRetorno = new RegistroInterno();
		regRetorno->setId(regData->getId());
		regRetorno->setRefHijo(nuevoContenedor->getSelfReference());

	} else {	// Es un nodo interno
		// Crea un nodo interno que guarde la referencia al hijo maximo del nodo
		RegistroAbstracto* regMax = (*nuevoContenedor)[nuevoContenedor->getCantRegistros()];
		regRetorno = new RegistroInterno();
		regRetorno->setId(regMax->getId());
		regRetorno->setRefHijo(nuevoContenedor->getRefenciaDerecha(nivelNodo));

		// Actualiza la referencia derecha del contenedor que sobrevive
		nuevoContenedor->setRefenciaDerecha(this->_referenciaExtra, nivelNodo);
	}

	// Se debe usar estos datos para actualizar el nodo interno padre
	return regRetorno;
}


void VectorRegistros::pack(char* &data){
	// Primero se guarda el tamaño total, la cant de registros y le referencia extra
	SerialInator serial;
	serial.serializar(data, static_cast<unsigned long>(this->_cantBytesOcupados) );
	serial.serializar(data, static_cast<unsigned long>(this->_listRegs.size()) );
	serial.serializar(data, this->_referenciaExtra );

	// Guarda una referencia donde ir agregando los datos
	char* ptr = serial.getPtroActual(data);

	// Se guardan los registros de a uno
	std::list<RegistroAbstracto*>::iterator it;
	for (it = this->_listRegs.begin(); it != this->_listRegs.end(); it++){
		RegistroAbstracto* reg = *it;

		// Almacenan los registros
		reg->pack(ptr);
		ptr += reg->getSize();
	}
}


void VectorRegistros::unpack(char* data, datosArchivados tipo, bool esHoja){
	// Primero viene el tamaño, la cantidad de regs y le referencia extra
	unsigned long int tam, cantRegs;
	SerialInator serial;
	serial.desSerializar(data, tam);
	serial.desSerializar(data, cantRegs);
	serial.desSerializar(data, this->_referenciaExtra);

	// Un puntero a los datos que pertenecen al primer registro
	char* ptrData = serial.getPtroActual(data);

	// Luego vienen los registros
	for (unsigned int i=0; i< cantRegs; i++){
		// Crea el registro correspondiente
		RegistroAbstracto* reg = RegistroAbstracto::unPack(ptrData, tipo, esHoja);
		ptrData += reg->getSize();

		// Agrega el registro al final
		this->_listRegs.push_back(reg);
		this->_espacioLibre -= reg->getSize();
		this->_cantBytesOcupados += reg->getSize();
	}
}


